package nz.ac.massey.cs.domain;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.java.plugin.Plugin;

import com.rits.cloning.Cloner;

import nc.ac.massey.cs.plugin.ManagePlugins;
import nz.ac.massey.cs.persistency.Sudoku_reader;
import nz.ac.massey.cs.persistency.Sudoku_reader.result;
import nz.ac.massey.cs.utils.CellType;
import nz.ac.massey.cs.utils.Constants;

public abstract class Sudoku extends Plugin implements ISudoku{
	private String blank = "0";
	private int rowSize = 9;

	public void setRowSize(int rowSize) {
		this.rowSize = rowSize;
	}

	public void setColSize(int colSize) {
		this.colSize = colSize;
	}

	private int colSize = 9;
	private int blockSize = 3;

	public void setBlockSize(int blockSize) {
		this.blockSize = blockSize;
	}

	// Available range of values for the specific Sudoku
	public  ArrayList<String> valueRangeOfCell = new ArrayList<String>();
	private int lowerRange = 1;
	private int upperRange = 9; // need to fix

	private ArrayList<IConstraint> constraints;
	private Cell[][] arrayCells = new Cell[colSize][rowSize];
	private String[][] cellConstraintInfo = new String[colSize][colSize];
	private Cloner cloner = new Cloner(); // this thing uses reflection we can claim it a part of pur project

	public Sudoku(){}

	@Override
	public void Build(result fileData) {
		setParameters();
		constraints = new ArrayList<IConstraint>(); 
		String[][] result = fileData.getCellInfo();
		for(int i = 0; i < result.length;i++)
		{
			for(int k = 0; k < result[1].length;k++)
			{
				if(result[i][k].contains("{")) 
				{
					String[] data = result[i][k].replace("}","").split("\\{");
					cellConstraintInfo[i][k] = data[1];
					if(data[0].equals(blank))
					{
						Cell current = new Cell(true, "", cloner.deepClone(this.valueRangeOfCell),i,k);
						current.setType(CellType.VARIABLE);
						arrayCells[i][k] = current; 

					}
					else
					{
						ArrayList<String> onlyValue =new ArrayList<String>();
						onlyValue.add(data[0]);
						Cell current = new Cell(false, data[0], onlyValue,i,k);
						current.setType(CellType.FIXED);
						arrayCells[i][k] = current;
					}				
				}
				else
				{
					// a condition to look for this value as a constraint
					if(result[i][k].equals(blank))
					{
						Cell current = new Cell(true, "", cloner.deepClone(this.valueRangeOfCell),i,k);
						current.setType(CellType.VARIABLE);
						arrayCells[i][k] = current; 

					}
					else
					{
						ArrayList<String> onlyValue =new ArrayList<String>();
						onlyValue.add(result[i][k]);
						Cell current = new Cell(false, result[i][k], onlyValue,i,k);
						current.setType(CellType.FIXED);
						arrayCells[i][k] = current;
					}
				}
			}
		}
		try
		{
			buildConstraintsFromFile(fileData);
		}
		catch(Exception e){}
		Customize();
	}

	/*
	 * sets the parameters for the sudoku being built
	 * must define the range, row and colum sizes at this point
	 */
	public abstract void setParameters();

	/*
	 * customise the constraints for the sudoku that are the same for all puzzles of this type
	 */
	public abstract void Customize();
	
	/*
	 * build up a range of numerical values from lower to upper
	 */
	public ArrayList<String> buildNumRange(int lower,int upper)
	{
		this.lowerRange = lower;
		this.upperRange = upper;
		ArrayList<String> result = new ArrayList<String>();
		for(int i=lower;i <= upper; i++)
		{
			result.add(String.valueOf(i));
		}
		valueRangeOfCell = result;
		return result;
	}
	
	/*
	 * sets the possible values for this sudoku from the array provide
	 * the recomended way to build the array is new String[]{"56","45","the"};
	 */
	public ArrayList<String> buildRangeFromStringArray(String[] possibleValues)
	{		
		ArrayList<String> result = new ArrayList<String>(Arrays.asList(possibleValues));
		this.valueRangeOfCell = result;
		return result;
	}

	/*
	 * builds the constraints from the file data
	 */
	private void buildConstraintsFromFile(result result)
	{
		Map<String,IConstraint> constraintsMap = new HashMap<String, IConstraint>();
		String[] data = result.getConstraintInfo();
		for(int i = 0; i < data.length;i++)
		{
			String[] oneData = data[i].split("=");
			String[] parameters = oneData[1].split(",");
			IConstraint constraint = ManagePlugins.getconstraintplugins(parameters[0]);
			constraint.setConstraintInfo(oneData[1], cloner.deepClone(this.valueRangeOfCell));			
			constraintsMap.put(oneData[0], constraint);
		}

		for(int i =0 ; i < cellConstraintInfo.length;i++)
		{
			for(int k =0; k < cellConstraintInfo[1].length;k++)
			{
				String[] cellConstraintData = cellConstraintInfo[i][k].split(",");
				for(int l = 0; l < cellConstraintData.length; l++)
				{
					constraintsMap.get(cellConstraintData[0]).addCell(arrayCells[i][k]);
					arrayCells[i][k].addConstraint(constraintsMap.get(cellConstraintData[0]));
				}
			}
		}
	}

	/*
	 * takes a 2 1-d array of cell references with x in first y in second
	 */
	public void buildConstraintWithCells(int[] cellYIds, int[] cellXIds, String constraintType, String constraintInfo)
	{
		IConstraint constraint = ManagePlugins.getconstraintplugins(constraintType);
		constraint.setConstraintInfo(constraintInfo, cloner.deepClone(this.valueRangeOfCell));
		for(int i =0;i < cellYIds.length;i++)
		{
			Cell cell = this.arrayCells[cellYIds[i]][cellXIds[i]];
			constraint.addCell(cell);
			cell.addConstraint(constraint);
		}
		this.constraints.add(constraint);
	}

	/*
	 * apply a constraint to each row
	 */
	public void forEachRow(String constraintType, String constraintInfo) 
	{
		for(int i = 0; i < colSize;i++)
		{
			IConstraint constraint = ManagePlugins.getconstraintplugins(constraintType);
			constraint.setConstraintInfo(constraintInfo, cloner.deepClone(this.valueRangeOfCell));

			for(int k = 0; k < rowSize;k++)
			{
				constraint.addCell(arrayCells[i][k]);
				arrayCells[i][k].addConstraint(constraint);
			}
			constraints.add(constraint);			
		}		
	}

	/*
	 * apply a constraint to each column
	 */
	public void forEachColumn(String constraintType, String constraintInfo)
	{
		for(int x = 0 ; x < colSize;x++)
		{
			IConstraint constraint = ManagePlugins.getconstraintplugins(constraintType);
			constraint.setConstraintInfo(constraintInfo,  cloner.deepClone(this.valueRangeOfCell));
			for(int y = 0; y < rowSize;y++)
			{
				constraint.addCell(arrayCells[y][x]);
				arrayCells[y][x].addConstraint(constraint);
			}
			constraints.add(constraint);
		}
	}

	/*
	 * creates a constraint for each square block 
	 * need to word the input better
	 * n^2
	 */
	public void forEachBlock(int n,int numberOfBlocks,String constraintType,String constraintInfo) // need to add code from home to make this work, as also needs the number of block of the given size
	{
		IConstraint[][] blockConstraints = new IConstraint[numberOfBlocks][numberOfBlocks];

		for(int i = 0; i < n;i++ )
		{
			for(int k = 0; k < n; k++)
			{
				blockConstraints[i][k] = ManagePlugins.getconstraintplugins(constraintType);
				blockConstraints[i][k].setConstraintInfo(constraintInfo,  cloner.deepClone(this.valueRangeOfCell));
			}
		}

		for(double x = 0; x < colSize; x++)
		{
			for(double y = 0; y < rowSize; y++)
			{
				blockConstraints[moddifyNumber(x, numberOfBlocks)][moddifyNumber(y, numberOfBlocks)].addCell(arrayCells[(int)x][(int)y]);
				arrayCells[(int)x][(int)y].addConstraint(blockConstraints[moddifyNumber(x, numberOfBlocks)][moddifyNumber(y, numberOfBlocks)]);
			}
		}

		for(int i = 0; i < n; i++)
		{
			for(int k = 0; k < n; k++)
			{
				constraints.add(blockConstraints[i][k]);
			}
		}
	}

	/*
	 * used by the for each block to moddify the numbers to get the right block
	 */
	private int moddifyNumber(double d, double e)
	{
		double result = d / e;
		return (int) result;
	}

	/*
	 * get the cells as a 2-dimensional array
	 */
	public Cell[][] getCells()
	{
		return arrayCells;
	}

	/*
	 * Retrieve cells in the same column by given a cell
	 */
	public ArrayList<Cell> getRowByCell (Cell cell) {

		ArrayList<Cell> cellsInColumn = new ArrayList<Cell>();
		for (int x = 0 ; x < this.getRowSize();x++)
		{
			for(int y =0; y < this.getColSize();y++)
			{
				if(cell.getRowIdx() == arrayCells[x][y].getRowIdx())
				{
					cellsInColumn.add(arrayCells[x][y]);
				}
			}
		}			
		return cellsInColumn;
	}

	/*
	 *  Retrieve cells in the same column by given a cell
	 */
	public ArrayList<Cell> getColumnByCell(Cell cell) {

		ArrayList<Cell> cellsInColumn = new ArrayList<Cell>();
		for (int x = 0 ; x < this.getRowSize();x++)
		{
			for(int y =0; y < this.getColSize();y++)
			{
				if(cell.getColIdx() == arrayCells[x][y].getColIdx())
				{
					cellsInColumn.add(arrayCells[x][y]);
				}
			}
		}			
		return cellsInColumn;
	}

	/*
	 * gives option to get cell by indexs if the limits are exceded then return null
	 */
	public Cell getCellByIndex(int rowIdx, int colIdx) {

		try
		{
			return this.arrayCells[rowIdx][colIdx];
		}
		catch (Exception e)
		{
			return null;
		}
	}

	/*
	 * test to see if the puzzle is complete
	 */
	public boolean puzzleComplete()
	{
		for(IConstraint con:this.constraints)
		{
			if(!con.isSatisfied())
			{
				return false;
			}
		}
		return true;
	}

	@Override
	public int getRowSize() {
		return this.rowSize;
	}

	@Override
	public int getColSize() {
		return this.colSize;
	}
}
